#define DEBUGPRINTS

#include <iostream>
using namespace std;

#include "db\db_cxx.h"
#include "db\db.h"


template <class T>
class DatabaseManager
{
private:
	Db *_db;
	
	Dbc *_cursor;
public:
	bool AddRecord(void *key, int keylength, T *data);
	bool AddRecord(char *key, T *data); //requires null-terminated string
	bool AddRawRecord(void *key, int keylength, void * data, int datalength);
	bool AddRawRecord(char *key, void * data, int datalength);


	bool RemoveRecord(void *key, int keylength);
	bool RemoveRecord(char *key); //requires null-terminated string

	T *GetRecord(void *key, int keylength); 
	T *GetRecord(char *key); //requires null-terminated string
	void* GetRawData(void *key, int keylength);
	void* GetRawData(char *key);
	// all Get<...> functions return NULL if no record correspond to <key>
	// and don't forget to free() the returned value!
	
	void Sync();

	bool Iterate(void *key, int *keylength, T* value);
	bool Iterate(char *key, T* value);

	void ResetIterator();

	DatabaseManager(char *filename, bool create_new);
	~DatabaseManager();



};



template <class T>
DatabaseManager<T>::~DatabaseManager()
{
	this->Sync();
	if (_cursor) _cursor->close();
	_db->close(0);
	//delete _db;
}

template <class T>
DatabaseManager<T>::DatabaseManager(char *filename, bool create_new)
{
	_db  = new Db(NULL, 0); // Instantiate the Db object
	_cursor = NULL;
	u_int32_t oFlags;
	
	if (create_new)
		oFlags = DB_CREATE; // Open flags;
	else
		oFlags = 0; // Open flags;
	
	try 
	{
	
		_db->open(NULL, // Transaction pointer
		filename, // Database file name
		NULL, // Optional logical database name
		DB_BTREE, // Database access method
		oFlags, // Open flags
		0); // File mode (using defaults)
	}

	catch(const DbException &e) 
	{
		// Error handling code goes here
		#ifdef DEBUGPRINTS
			cout << e.what();
		#endif
		
		throw;
	}
	catch(const std::exception &e) 
	{
		#ifdef DEBUGPRINTS
			cout << e.what();
		#endif

		throw;
	}

}

template <class T>
bool DatabaseManager<T>::AddRawRecord(void *key, int keylength, void *data, int datalength)
{
	Dbt p1, p2;
	
	p1.set_flags(DB_DBT_MALLOC);
	p1.set_size(keylength);
	p1.set_data(key);

	p2.set_flags(DB_DBT_MALLOC);
	p2.set_size(datalength);
	p2.set_data(data);
	int res;
	try
	{
		res = _db->put(0, &p1, &p2, DB_NOOVERWRITE);
	}
	catch(const DbException &e) 
	{
		#ifdef DEBUGPRINTS
			cout << e.what();
		#endif	
		
		return false;
	}

	if (res != 0)
	{
		#ifdef DEBUGPRINTS
			cout << db_strerror(res);	
		#endif
		return false;
	}

	return true;
}



template <class T>
bool DatabaseManager<T>::AddRawRecord(char *key, void *data, int datalength)
{
	return this->AddRawRecord((void*)key, strlen(key), data, datalength);
}

template <class T>
bool DatabaseManager<T>::AddRecord(void *key, int keylength, T* data)
{
	return this->AddRawRecord(key, keylength, data, sizeof T);
}

template <class T>
bool DatabaseManager<T>::AddRecord(char *key, T* data)
{
	return this->AddRecord(key, strlen(key), data);
}


template <class T>
void *DatabaseManager<T>::GetRawData(void *key, int keylength)
{

		Dbt p1, p2;
		
		p1.set_flags(DB_DBT_MALLOC);
		p1.set_size(keylength);
		p1.set_data(key);



		p2.set_flags(DB_DBT_MALLOC);
		p2.set_size(sizeof T);

		int res;
		try
		{
			res = _db->get(0, &p1, &p2, 0);
		}
		catch (const DbException &e)
		{
			#ifdef DEBUGPRINTS
				cout << e.what();
			#endif
			return 0;
		}
		if (res != 0)
		{
			#ifdef DEBUGPRINTS
				cout << db_strerror(res);
			#endif
			return 0;
		}

		return p2.get_data();


}


template <class T>
T *DatabaseManager<T>::GetRecord(void *key, int keylength)
{
	return (T*)this->GetRawData(key, keylength);
}



template <class T>
void *DatabaseManager<T>::GetRawData(char *key)
{
	return this->GetRecord((void*)key, strlen(key));
}

template <class T>
T *DatabaseManager<T>::GetRecord(char *key)
{
	return (T*)this->GetRawData(key);
}

template <class T>
void DatabaseManager<T>::Sync()
{
	_db->sync(0);
}

template <class T>
bool DatabaseManager<T>::RemoveRecord(void *key, int keylength)
{
	Dbt k;
	int res = 0;

	k.set_size(keylength);
	k.set_data(key);
	try
	{
		res = _db->del(0, k, 0);
	}
	catch (const DbException &e)
	{
		#ifdef DEBUGPRINTS
			cout << e.what();
		#endif
		return false;
	}

	if (res != 0)
	{
		#ifdef DEBUGPRINTS
			cout << db_strerror(res);
		#endif

		return false;
	}

	return true;
}


template <class T>
bool DatabaseManager<T>::RemoveRecord(char *key)
{
	return this->RemoveRecord((void*)key, strlen(key));

}

template <class T>
bool DatabaseManager<T>::Iterate(void *key, int *keylength, T* value)
{

	Dbt dbkey, dbdata;
	dbkey.set_flags(DB_DBT_MALLOC);
	dbdata.set_flags(DB_DBT_MALLOC);


	static inited = false;
	if(!inited)
	{
		try
		{
			_db->cursor(0, &_cursor, 0);
		}
		catch(const DbException &e)
		{
			#ifdef DEBUGPRINTS
				cout << e.what();
			#endif
			inited = false;
			return false;
		}
		inited = true; //all ok
	}
	
	int res;

	if(res =_cursor->get(&dbkey, &dbdata, DB_NEXT))
	{
		#ifdef DEBUGPRINTS

			if (res != DB_NOTFOUND)
				cout << res << "   " << db_strerror(res);
		#endif
		
		inited = false;
		return false;
	}

	
	memcpy(key, dbkey.get_data(), dbkey.get_size());
	*keylength = dbkey.get_size();
	memcpy(value, dbdata.get_data(), dbdata.get_size());
	return true;
}

template <class T>
bool DatabaseManager<T>::Iterate(char *key, T* value)
{
	int i;
	this->Iterate(key, &i, value);
}


template <class T>
void DatabaseManager<T>::ResetIterator()
{
	_db->cursor(0, &_cursor, 0);
}